/*
 * Copyright (C) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "ohos_utils/napi_handler.h"
#include "ohos_utils/ohos_log.h"
#include <map>
#include <sentry.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>

static napi_value nativeSetTag(napi_env env, napi_callback_info info) {
    NapiHandler napiHandler(env, info, PARAM_COUNT_2);
    std::string key = napiHandler.ParseArgAs<std::string>(INDEX_0);
    std::string value = napiHandler.ParseArgAs<std::string>(INDEX_1);
    sentry_set_tag(key.c_str(), value.c_str());
    return napiHandler.GetVoidValue();
}

static napi_value nativeRemoveTag(napi_env env, napi_callback_info info) {
    NapiHandler napiHandler(env, info, PARAM_COUNT_1);
    std::string key = napiHandler.ParseArgAs<std::string>(INDEX_0);
    sentry_remove_tag(key.c_str());
    return napiHandler.GetVoidValue();
}

static napi_value nativeSetExtra(napi_env env, napi_callback_info info) {
    NapiHandler napiHandler(env, info, PARAM_COUNT_2);
    std::string key = napiHandler.ParseArgAs<std::string>(INDEX_0);
    std::string value = napiHandler.ParseArgAs<std::string>(INDEX_1);

    sentry_value_t sentryValue = sentry_value_new_string(value.c_str());
    sentry_set_extra(key.c_str(), sentryValue);
    return napiHandler.GetVoidValue();
}

static napi_value nativeRemoveExtra(napi_env env, napi_callback_info info) {
    NapiHandler napiHandler(env, info, PARAM_COUNT_1);
    std::string key = napiHandler.ParseArgAs<std::string>(INDEX_0);

    sentry_remove_extra(key.c_str());
    return napiHandler.GetVoidValue();
}

static napi_value nativeSetUser(napi_env env, napi_callback_info info) {
    NapiHandler napiHandler(env, info, PARAM_COUNT_4);
    std::string id = napiHandler.ParseArgAs<std::string>(INDEX_0);
    std::string email = napiHandler.ParseArgAs<std::string>(INDEX_1);
    std::string ipAddress = napiHandler.ParseArgAs<std::string>(INDEX_2);
    std::string username = napiHandler.ParseArgAs<std::string>(INDEX_3);
    sentry_value_t user = sentry_value_new_object();
    sentry_value_set_by_key(user, "id", sentry_value_new_string(id.c_str()));
    sentry_value_set_by_key(user, "email", sentry_value_new_string(email.c_str()));
    sentry_value_set_by_key(user, "ip_address", sentry_value_new_string(ipAddress.c_str()));
    sentry_value_set_by_key(user, "username", sentry_value_new_string(username.c_str()));
    sentry_set_user(user);
    return napiHandler.GetVoidValue();
}

static napi_value nativeRemoveUser(napi_env env, napi_callback_info info) {
    NapiHandler napiHandler(env, info, 0);
    sentry_remove_user();
    return napiHandler.GetVoidValue();
}

static napi_value nativeSetTrace(napi_env env, napi_callback_info info) {
    NapiHandler napiHandler(env, info, PARAM_COUNT_2);
    std::string traceId = napiHandler.ParseArgAs<std::string>(INDEX_0);
    std::string spanId = napiHandler.ParseArgAs<std::string>(INDEX_1);

    sentry_set_trace(traceId.c_str(), spanId.c_str());
    return napiHandler.GetVoidValue();
}

static napi_value nativeAddBreadcrumb(napi_env env, napi_callback_info info) {
    NapiHandler napiHandler(env, info, PARAM_COUNT_6);
    std::string level = napiHandler.ParseArgAs<std::string>(INDEX_0);
    std::string message = napiHandler.ParseArgAs<std::string>(INDEX_1);
    std::string category = napiHandler.ParseArgAs<std::string>(INDEX_2);
    std::string type = napiHandler.ParseArgAs<std::string>(INDEX_3);
    std::string timestamp = napiHandler.ParseArgAs<std::string>(INDEX_4);
    std::string data = napiHandler.ParseArgAs<std::string>(INDEX_5);

    sentry_value_t crumb = sentry_value_new_breadcrumb(type.c_str(), message.c_str());
    sentry_value_set_by_key(crumb, "category", sentry_value_new_string(category.c_str()));
    sentry_value_set_by_key(crumb, "level", sentry_value_new_string(level.c_str()));
    sentry_value_set_by_key(crumb, "timestamp", sentry_value_new_string(timestamp.c_str()));
    sentry_value_t dataObject = sentry_value_new_object();
    sentry_value_set_by_key(dataObject, "data", sentry_value_new_string(data.c_str()));
    sentry_value_set_by_key(crumb, "data", dataObject);
    sentry_add_breadcrumb(crumb);
    return napiHandler.GetVoidValue();
}

static void send_envelope(sentry_envelope_t *envelope, void *data) {
    const char *outbox_path = (const char *)data;
    char envelope_id_str[40];

    sentry_uuid_t envelope_id = sentry_uuid_new_v4();
    sentry_uuid_as_string(&envelope_id, envelope_id_str);

    size_t outbox_len = strlen(outbox_path);
    size_t final_len = outbox_len + 42; // "/" + envelope_id_str + "\0" = 42
    char *envelope_path = (char *)sentry_malloc(final_len);
    if (envelope_path == nullptr) {
        return;
    }
    int written = snprintf(envelope_path, final_len, "%s/%s", outbox_path, envelope_id_str);
    if (written > outbox_len && written < final_len) {
        sentry_envelope_write_to_file(envelope, envelope_path);
    }

    sentry_free(envelope_path);
    sentry_envelope_free(envelope);
}

static napi_value initSentryNative(napi_env env, napi_callback_info info) {
    NapiHandler napiHandler(env, info, PARAM_COUNT_1);
    std::string outbox_path = napiHandler.GetObjectProp<std::string>(INDEX_0, "outboxPath");
    std::string dsn = napiHandler.GetObjectProp<std::string>(INDEX_0, "dsn");
    bool isDebug = napiHandler.GetObjectProp<bool>(INDEX_0, "isDebug");
    std::string release = napiHandler.GetObjectProp<std::string>(INDEX_0, "release");
    std::string environment = napiHandler.GetObjectProp<std::string>(INDEX_0, "environment");
    std::string dist = napiHandler.GetObjectProp<std::string>(INDEX_0, "dist");
    int maxBreadcrumbs = napiHandler.GetObjectProp<int>(INDEX_0, "maxBreadcrumbs");
    std::string sdkName = napiHandler.GetObjectProp<std::string>(INDEX_0, "sdkName");
    int ndkHandlerStrategy = napiHandler.GetObjectProp<int>(INDEX_0, "ndkHandlerStrategy");
    double tracesSampleRate = napiHandler.GetObjectProp<double>(INDEX_0, "tracesSampleRate");

    sentry_transport_t *transport = NULL;
    bool transport_owns_path = false;
    sentry_options_t *options = NULL;
    bool options_owns_transport = false;

    options = sentry_options_new();
    if (options == nullptr) {
        return napiHandler.GetVoidValue();
    }

    // session tracking is enabled by default, but the Android SDK already handles it
    sentry_options_set_auto_session_tracking(options, 0);
    sentry_options_set_debug(options, isDebug);
    sentry_options_set_max_breadcrumbs(options, maxBreadcrumbs);
    options_owns_transport = true;

    // give sentry-native its own database path it can work with, next to the outbox
    std::string database_path = outbox_path;
    size_t pos = database_path.find_last_of('/');
    if (pos != std::string::npos) {
        database_path = database_path.substr(0, pos + 1) + database_path.substr(pos + 1) + ".sentry-native";
    } else {
        database_path += ".sentry-native";
    }
    sentry_options_set_database_path(options, database_path.c_str());

    sentry_options_set_dsn(options, dsn.c_str());
    sentry_options_set_release(options, release.c_str());
    sentry_options_set_environment(options, environment.c_str());
    sentry_options_set_dist(options, dist.c_str());
    sentry_options_set_sdk_name(options, sdkName.c_str());
    sentry_options_set_handler_strategy(options, static_cast<sentry_handler_strategy_t>(ndkHandlerStrategy));
    sentry_options_set_traces_sample_rate(options, tracesSampleRate);
    LOGD("sample rate: %f", tracesSampleRate);

    sentry_init(options);
    return napiHandler.GetVoidValue();
}

static napi_value nativeClearModuleList(napi_env env, napi_callback_info info) {
    (void)env;
    (void)info;
    sentry_clear_modulecache();
    return nullptr;
}

static napi_value nativeLoadModuleList(napi_env env, napi_callback_info info) {
    NapiHandler napiHandler(env, info, 0);
    sentry_value_t image_list_t = sentry_get_modules_list();
    napi_value image_list = nullptr;

    if (sentry_value_get_type(image_list_t) == SENTRY_VALUE_TYPE_LIST) {
        size_t len_t = sentry_value_get_length(image_list_t);

        image_list = napiHandler.CreateArray(len_t, [&](int i, napi_value &obj) {
            sentry_value_t image_t = sentry_value_get_by_index(image_list_t, i);

            if (!sentry_value_is_null(image_t)) {
                sentry_value_t image_addr_t = sentry_value_get_by_key(image_t, "image_addr");
                if (!sentry_value_is_null(image_addr_t)) {
                    const char *value_v = sentry_value_as_string(image_addr_t);
                    napiHandler.SetObjectProp<std::string>(obj, "imageAddr", std::string(value_v));
                }

                sentry_value_t image_size_t = sentry_value_get_by_key(image_t, "image_size");
                if (!sentry_value_is_null(image_size_t)) {
                    int32_t value_v = sentry_value_as_int32(image_size_t);
                    napiHandler.SetObjectProp<int32_t>(obj, "imageSize", value_v);
                }

                sentry_value_t code_file_t = sentry_value_get_by_key(image_t, "code_file");
                if (!sentry_value_is_null(code_file_t)) {
                    const char *value_v = sentry_value_as_string(code_file_t);
                    napiHandler.SetObjectProp<std::string>(obj, "codeFile", std::string(value_v));
                }

                sentry_value_t code_type_t = sentry_value_get_by_key(image_t, "type");
                if (!sentry_value_is_null(code_type_t)) {
                    const char *value_v = sentry_value_as_string(code_type_t);
                    napiHandler.SetObjectProp<std::string>(obj, "type", std::string(value_v));
                }

                sentry_value_t debug_id_t = sentry_value_get_by_key(image_t, "debug_id");
                if (!sentry_value_is_null(code_type_t)) {
                    const char *value_v = sentry_value_as_string(debug_id_t);
                    napiHandler.SetObjectProp<std::string>(obj, "debugId", std::string(value_v));
                }

                sentry_value_t code_id_t = sentry_value_get_by_key(image_t, "code_id");
                if (!sentry_value_is_null(code_id_t)) {
                    const char *value_v = sentry_value_as_string(code_id_t);
                    napiHandler.SetObjectProp<std::string>(obj, "codeId", std::string(value_v));
                }

                // not needed on Android, but keeping for forward compatibility
                sentry_value_t debug_file_t = sentry_value_get_by_key(image_t, "debug_file");
                if (!sentry_value_is_null(debug_file_t)) {
                    const char *value_v = sentry_value_as_string(debug_file_t);
                    napiHandler.SetObjectProp<std::string>(obj, "debugFile", std::string(value_v));
                }
            }
        });

        sentry_value_decref(image_list_t);
    }

    return image_list;
}

static napi_value shutdown(napi_env env, napi_callback_info info) {
    (void)env;
    (void)info;
    sentry_close();
    return nullptr;
}

static napi_value message(napi_env env, napi_callback_info info) {
    LOGD("Sending message.");
    sentry_value_t event = sentry_value_new_message_event(
        /*   level */ SENTRY_LEVEL_INFO,
        /*  logger */ "custom",
        /* message */ "It works!");
    sentry_uuid_s uuid = sentry_capture_event(event);
    bool fail = sentry_uuid_is_nil(&uuid);
    if (!fail) {
        LOGD("Message was sent.");
    } else {
        LOGE("Message was not sent.");
    }
    napi_value result;
    napi_get_boolean(env, !fail, &result);
    return result;
}

napi_value transaction(napi_env env, napi_callback_info info) {
    LOGD("Sending transaction.");
    sentry_transaction_context_t *tx_ctx =
        sentry_transaction_context_new("little.teapot", "Short and stout here is my handle and here is my spout");
    sentry_value_t custom_sampling_ctx = sentry_value_new_object();
    sentry_value_set_by_key(custom_sampling_ctx, "b", sentry_value_new_int32(42));
    sentry_transaction_t *tx = sentry_transaction_start(tx_ctx, custom_sampling_ctx);
    sentry_transaction_set_data(tx, "url", sentry_value_new_string("https://example.com"));
    sentry_span_t *child = sentry_transaction_start_child(tx, "littler.teapot", NULL);
    sentry_span_t *grandchild = sentry_span_start_child(child, "littlest.teapot", NULL);

    sentry_span_set_data(child, "span_data_says", sentry_value_new_string("hi!"));
    sentry_span_finish(grandchild);
    sentry_span_finish(child);
    sentry_uuid_s uuid = sentry_transaction_finish(tx);
    bool success = false;
    if (sentry_uuid_is_nil(&uuid)) {
        success = false;
        LOGE("Transaction was not sent.");
    } else {
        success = true;
        LOGD("Transaction sent.");
    }
    napi_value result;
    napi_get_boolean(env, success, &result);
    return result;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        DECLARE_NAPI_FUNCTION("nativeSetTag", nativeSetTag),
        DECLARE_NAPI_FUNCTION("nativeRemoveTag", nativeRemoveTag),
        DECLARE_NAPI_FUNCTION("nativeSetExtra", nativeSetExtra),
        DECLARE_NAPI_FUNCTION("nativeRemoveExtra", nativeRemoveExtra),
        DECLARE_NAPI_FUNCTION("nativeSetUser", nativeSetUser),
        DECLARE_NAPI_FUNCTION("nativeRemoveUser", nativeRemoveUser),
        DECLARE_NAPI_FUNCTION("nativeSetTrace", nativeSetTrace),
        DECLARE_NAPI_FUNCTION("nativeAddBreadcrumb", nativeAddBreadcrumb),
        DECLARE_NAPI_FUNCTION("initSentryNative", initSentryNative),
        DECLARE_NAPI_FUNCTION("nativeClearModuleList", nativeClearModuleList),
        DECLARE_NAPI_FUNCTION("nativeLoadModuleList", nativeLoadModuleList),
        DECLARE_NAPI_FUNCTION("shutdown", shutdown),
        DECLARE_NAPI_FUNCTION("message", message),
        DECLARE_NAPI_FUNCTION("transaction", transaction),
    };
    NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
    return exports;
}
EXTERN_C_END

static napi_module sentryModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "sentry_native_napi",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterModule(void) { napi_module_register(&sentryModule); }
